/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       sky.cpp
 * Author:     karooolek
 * Created on: Jan 4, 2010
 *
 **********************************************************************************************************************/

#include "sky.h"

namespace mGameEngine
{
namespace Game
{

using namespace Graphics;

Sky::Sky() :
    Drawable(),
    _skyGradient(NULL), _darkness(0.5f),
    _sun(), _lightness(0.25f), _sunSize(rad(0.55f)),
    _environmentMap(Vector2(256, 256), TEX_WRAP_CLAMP, TEX_FILTER_LINEAR)
{
    addDrawable(this);
    addLight(&_sun);

    _boundingSphere.setRadius(999999999999.9f);
}

Sky::~Sky()
{
}

void Sky::setSunColor(const Color &sunColor)
{
    _sun.setDiffuseColor(sunColor);
    _sun.setSpecularColor(sunColor);

    // set clouds layers
    for(List<Clouds *>::ConstIterator i = _clouds.begin(); i != _clouds.end(); ++i)
    {
        Clouds *clouds = *i;

        clouds->setSunColor(sunColor);
    }
}

void Sky::setSunDirection(const Vector3 &dir)
{
    Vector3 p(dir);
    p.normalize();

    _sun.setDirection(p);

    // set clouds layers
    for(List<Clouds *>::ConstIterator i = _clouds.begin(); i != _clouds.end(); ++i)
    {
        Clouds *clouds = *i;
        clouds->setSunDirection(p);
    }
    
    // set sun color based on position
    float x = -p.z / 0.2f;
    Color sunColor = Color(clamp(1.5f * x + 0.66667f, 0.0f, 1.0f),
                           clamp(x + 0.33333f, 0.0f, 1.0f),
                           clamp(x + 0.0f, 0.0f, 1.0f));
    setSunColor(sunColor);
}

void Sky::setSunDirection(const Vector2 &coords, float day, float hour)
{
    // earth angle on solar orbit
    float theta = 2.0f * M_PI * (day + 10.0f) / 365.0f;

    // earth axis deviation from solar orbit
    Quaternion q(0.0f, rad(23.5f), 0.0f);

    // rotation around earth axis
    q *= Quaternion(2.0f * M_PI * hour / 24.0f + theta, 0.0f, 0.0f);

    // lattitude
    q *= Quaternion(0.0f, -rad(coords.y), 0.0f);

    // earth tranformation matrix
    Matrix4x4 t(q, Vector3(cos(theta), sin(theta), 0.0f));
    t *= Matrix4x4(0.0f, 0.0f, 1.0f, 0.0f,
                   1.0f, 0.0f, 0.0f, 0.0f,
                   0.0f, 1.0f, 0.0f, 0.0f,
                   0.0f, 0.0f, 0.0f, 1.0f);
    t.invert();

    // set position and time of day
    setSunDirection(-Vector3(t.m[3]));
}

void Sky::addClouds(Clouds *clouds)
{
    _clouds.push_back(clouds);
}

void Sky::removeClouds(Clouds *clouds)
{
    _clouds.remove(clouds);
}

void Sky::generateEnvironmentMap() const
{
    // rewrite this sh*t properly, plz o_O

    PerspectiveCamera cam(M_PI_2, 1.0f, false);
    cam.use();

    _environmentMap.setActiveFace(TEX_CUBE_X_POSITIVE);
    _environmentMap.beginRendering();
    gluLookAt(0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
    draw();
    _environmentMap.endRendering();

    _environmentMap.setActiveFace(TEX_CUBE_X_NEGATIVE);
    _environmentMap.beginRendering();
    gluLookAt(0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
    draw();
    _environmentMap.endRendering();
    
    _environmentMap.setActiveFace(TEX_CUBE_Y_POSITIVE);
    _environmentMap.beginRendering();
    gluLookAt(0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);
    draw();
    _environmentMap.endRendering();
    
    _environmentMap.setActiveFace(TEX_CUBE_Y_NEGATIVE);
    _environmentMap.beginRendering();
    gluLookAt(0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0);
    draw();
    _environmentMap.endRendering();
    
    _environmentMap.setActiveFace(TEX_CUBE_Z_POSITIVE);
    _environmentMap.beginRendering();
    gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0);
    draw();
    _environmentMap.endRendering();

    _environmentMap.setActiveFace(TEX_CUBE_Z_NEGATIVE);
    _environmentMap.beginRendering();
    gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0);
    draw();
    _environmentMap.endRendering();
}

void Sky::draw() const
{
    static GPUProgram gpuProgram(
#include "sky.vert"
            ,
#include "sky.frag"
            , true);

    // bind gpu program
    gpuProgram.use();
    gpuProgram.setTexture("skyGradient", _skyGradient, 0);
    gpuProgram.setValue("darkness", _darkness);
    gpuProgram.setVector("sunColor", Vector4(_sun.getDiffuseColor()));
    gpuProgram.setValue("lightness", _lightness);
    gpuProgram.setVector("sunPosition", -_sun.getDirection());
    gpuProgram.setValue("sunSizeSin", sin(0.5f * _sunSize));
    gpuProgram.setValue("sunSizeCos", cos(0.5f * _sunSize));

    // render cube
    DISPLAY_LIST
    (
        glDisable(GL_DEPTH_TEST);

        glBegin(GL_QUADS);
//            glVertex3fv(Vector3(-1.0f, -1.0f, -1.0f));
//            glVertex3fv(Vector3(1.0f, -1.0f, -1.0f));
//            glVertex3fv(Vector3(1.0f, 1.0f, -1.0f));
//            glVertex3fv(Vector3(-1.0f, 1.0f, -1.0f));

            glVertex3fv(Vector3(1.0f, -1.0f, -0.5f));
            glVertex3fv(Vector3(-1.0f, -1.0f, -0.5f));
            glVertex3fv(Vector3(-1.0f, -1.0f, 1.0f));
            glVertex3fv(Vector3(1.0f, -1.0f, 1.0f));


            glVertex3fv(Vector3(-1.0f, -1.0f, -0.5f));
            glVertex3fv(Vector3(-1.0f, 1.0f, -0.5f));
            glVertex3fv(Vector3(-1.0f, 1.0f, 1.0f));
            glVertex3fv(Vector3(-1.0f, -1.0f, 1.0f));

            glVertex3fv(Vector3(1.0f, -1.0f, 1.0f));
            glVertex3fv(Vector3(1.0f, 1.0f, 1.0f));
            glVertex3fv(Vector3(1.0f, 1.0f, -0.5f));
            glVertex3fv(Vector3(1.0f, -1.0f, -0.5f));


            glVertex3fv(Vector3(1.0f, 1.0f, -0.5f));
            glVertex3fv(Vector3(1.0f, 1.0f, 1.0f));
            glVertex3fv(Vector3(-1.0f, 1.0f, 1.0f));
            glVertex3fv(Vector3(-1.0f, 1.0f, -0.5f));

            glVertex3fv(Vector3(-1.0f, 1.0f, 1.0f));
            glVertex3fv(Vector3(1.0f, 1.0f, 1.0f));
            glVertex3fv(Vector3(1.0f, -1.0f, 1.0f));
            glVertex3fv(Vector3(-1.0f, -1.0f, 1.0f));
        glEnd();

        glEnable(GL_DEPTH_TEST);
    );

    // draw clouds layers
    for(List<Clouds *>::ConstIterator i = _clouds.begin(); i != _clouds.end(); ++i)
    {
        Clouds *clouds = *i;
        clouds->draw();
    }
}

}
}


